package com.timo.mail.util;


import com.timo.mail.constants.MailConstants;
import com.timo.mail.model.MailBoxService;
import com.timo.mail.model.MailFile;
import com.timo.mail.model.MailMessage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;


import static com.timo.mail.constants.MailConstants.MailTimeOut.*;

/**
 * @Author: Timo
 * @Date: 2019/6/4 11:01
 * @Description: 邮件消息接收解析类，
 * 现在已存在的接收邮件的类型分别是pop/pop3或者是imap/impa3/impa4/等类型
 */
public class ReceiveMailHandler {
    /**
     * 日志
     */
    Logger log = LoggerFactory.getLogger(ReceiveMailHandler.class);
    /**
     * 消息体
     */
    private MimeMessage mimeMessage = null;
    /**
     * 存放邮件内容的StringBuffer对象
     */
    private StringBuffer bodytext = new StringBuffer();
    /**
     * 附件下载后的存放目录
     */
    private String saveAttachPath = "";
    /**
     * 邮件地址返回分隔符
     */
    public static final String ADDRESS_SEPARATOR = ";";
    /**
     * 邮件地址返回格式字符串如：123456<123456@qq.com>; 123456<123456@qq.com>,1234567<1234567@qq.com>
     */
    public static final String ADDRESS_RESULT = "%s<%s>";

    //buffer size used for reading and writing
    private static final int BUFFER_SIZE = 8192;

    private MailBoxService mailBoxService;

    private boolean connect = false;

    private Properties properties;

    private Session session;

    private Store store;


    /**
     * 构造器
     *
     * @param mailBoxService 接收邮件的邮箱相关信息
     */
    public ReceiveMailHandler(MailBoxService mailBoxService) {
        this.mailBoxService = mailBoxService;
        this.properties = getProperties();
        this.session = getMailSession();
        this.store = getStore();
    }

    /**
     * 构造器
     *
     * @param saveAttachPath 附件的保存地址
     * @param mailBoxService 接收邮件的邮箱相关信息
     */
    public ReceiveMailHandler(String saveAttachPath, MailBoxService mailBoxService) {
        this.saveAttachPath = saveAttachPath;
        this.mailBoxService = mailBoxService;
        this.session = getMailSession();
        this.store = getStore();
    }

    /**
     * 检测是否连接成功
     *
     * @return
     */
    public boolean connect() {
        if (store.isConnected()) {
            return true;
        }

        int count = 1;

        while (!store.isConnected() && count < 4) {
            try {
                if (!connect) {
                    log.info("再次尝试连接 ...... ", count);
                    store.connect(mailBoxService.getHost(), mailBoxService.getAddress(), mailBoxService.getPassword());
                    this.connect = store.isConnected();
                }
            } catch (MessagingException e) {
                System.err.println("连接失败，再次尝试连接 handshake_count:" + count);


                count++;
                if (count >= 4) {
                    e.printStackTrace();
                }
            }
        }


        return connect;
    }


    /**
     * #############################################################
     * #                        对外暴露的方法
     * #############################################################
     */


    /**
     * 接收邮件
     * 要获取邮件中的附件和正文中的图片
     *
     * @param oldMessageId
     */
    public void receiveNewMail(List<String> oldMessageId) {
        if (!connect()) {
            System.out.println("connect is fail");
        }

        Folder folder = null;
        try {
            // 获取邮件的收件箱
            folder = store.getFolder("INBOX");
            // 标记为读写
            folder.open(Folder.READ_WRITE);
            Message[] messages = getInboxNewMessage(folder, listIsNotBlank(oldMessageId));

            System.out.println(messages.length);

            int count = 1;
            for (Message message : messages) {

                message = getMailMimeMessage((MimeMessage) message);
                System.out.println(decodeText("count:" + count + " >>" + message.getSubject()));

                // 判断是否有附件存在
                if (isContainAttachment((MimeMessage) message)) {
                    System.out.println("有附件存在.....");
                    saveAttachment(message, MailConstants.FILE_SAVE_PATH);
                }
                count++;

            }

        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }


    public void receiveNewMailThread(List<String> oldMessageId) {
        long startTime = System.currentTimeMillis();
        System.err.println("------------------------------ 开始 -------------------------");
        if (!connect()) {
            System.out.println("connect is fail");
        }

        Folder folder = null;
        try {

            // 获取邮件的收件箱
            folder = store.getFolder("INBOX");
            // 标记为读写
            folder.open(Folder.READ_WRITE);
            Message[] messages = getInboxNewMessage(folder, listIsNotBlank(oldMessageId));
            System.out.println(messages.length);



            int allCount = messages.length;// 所有的邮件
            int size = allCount > 10 ? 10 : 1;
            int averageCount = (int) Math.floor(allCount / size);// 向下取整
            ExecutorService pools = Executors.newFixedThreadPool(size);
            CompletionService<List<String>> cs = new ExecutorCompletionService<List<String>>(pools);


            for (int i = 0; i < size; i++) {
                int count = i * averageCount;
                if ((size - 1) <= i) {
                    cs.submit(new MailMessageCallable(Arrays.copyOfRange(messages,count,allCount)));
                }else {
                    cs.submit(new MailMessageCallable(Arrays.copyOfRange(messages,count,(i+1)*averageCount)));
                }

            }


            List<String> allList = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                Future<List<String>> f = cs.take();
                allList.addAll(f.get());
            }



            System.out.println(allList.size());
            long endTime = System.currentTimeMillis();
            System.err.println("------------------------------ 结束"+((endTime-startTime)/1000)+" -------------------------");
        }catch (Exception e) {
            e.printStackTrace();
        }


    }



    class MailMessageCallable implements Callable<List<String>>{

        private Message[] messages;
        List<String> list = new ArrayList<>();

        public MailMessageCallable(Message[] messages) {
            System.out.println(Thread.currentThread().getName()+">>>>>解析邮件的条数："+messages.length);
            this.messages = messages;
        }

        @Override
        public List<String> call() throws Exception {
            String subject = "";
            try {
                int count = 1;
                for (Message message : messages) {
                    message = getMailMimeMessage((MimeMessage) message);

                    subject = message.getSubject();
                    System.out.println(count+ " "
                            +Thread.currentThread().getName()
                            +">>>>>主题："+subject
                            +" 收件人："+getMailAddress((MimeMessage) message));
                    list.add(message.getSubject());
                    count++;
                }
                System.out.println("------------------------解析完毕--------------------");
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(">>>>>>>>>>>>>> 出错了:"+subject);
            }
            return list;
        }


    }

    /**
     * #############################################################
     * #                        私有方法
     * #############################################################
     */

    private void init() {
        this.properties = getProperties();
        this.session = getMailSession();
        this.store = getStore();
    }


    /**
     * 环境变量配置
     * <p>
     * <p>
     * mail.imap.user	串	IMAP的默认用户名。
     * mail.imap.host	串	要连接的IMAP服务器。
     * mail.imap.port	INT	要连接的IMAP服务器端口，如果connect（）方法没有显式指定一个。默认为143。
     * mail.imap.partialfetch	布尔	控制是否应使用IMAP部分提取功能。默认为true。
     * mail.imap.fetchsize	INT	部分提取大小（以字节为单位）。默认为16K。
     * mail.imap.ignorebodystructuresize	布尔	IMAP BODYSTRUCTURE响应包括每个身体部位的确切大小。通常，此大小用于确定每个身体部位要获取的数据量。默认为false。
     * mail.imap.connectiontimeout	INT	套接字连接超时值，以毫秒为单位。默认为无限超时。
     * mail.imap.timeout	INT	套接字I / O超时值，以毫秒为单位。默认为无限超时。
     * mail.imap.statuscachetimeout	INT	STATUS命令响应缓存的超时值（以毫秒为单位）。默认值为1000（1秒）。零禁用缓存。
     * mail.imap.appendbuffersize	INT	附加到IMAP文件夹时要在内存中缓冲的消息的最大大小。
     * mail.imap.connectionpoolsize	INT	连接池中的最大可用连接数。默认值为1。
     * mail.imap.connectionpooltimeout	INT	连接池连接的超时值（以毫秒为单位）。默认值为45000（45秒）。
     * mail.imap.separatestoreconnection	布尔	用于指示是否对存储命令使用专用存储连接的标志。默认值为false。
     * mail.imap.auth.login.disable	布尔	如果为true，则禁止使用非标准AUTHENTICATE LOGIN命令，而是使用plain LOGIN命令。默认值为false。
     * mail.imap.auth.plain.disable	布尔	如果为true，则阻止使用AUTHENTICATE PLAIN命令。默认值为false。
     * mail.imap.auth.ntlm.disable	布尔	如果为true，则阻止使用AUTHENTICATE NTLM命令。默认值为false。
     * mail.imap.proxyauth.user	串	如果服务器支持PROXYAUTH扩展，则此属性指定要充当的用户的名称。使用管理员凭据对服务器进行身份验证。身份验证后，IMAP提供程序将发出PROXYAUTH命令，其中包含此属性中指定的用户名。
     * mail.imap.localaddress	串	创建IMAP套接字时要绑定的本地地址（主机名）。默认为Socket类选择的地址。
     * mail.imap.localport	INT	创建IMAP套接字时要绑定的本地端口号。默认为Socket类选择的端口号。
     * mail.imap.sasl.enable	布尔	如果设置为true，请尝试使用javax.security.sasl包来选择登录的身份验证机制。默认为false。
     * mail.imap.sasl.mechanisms	串	要尝试使用的空格或逗号分隔的SASL机制名称列表。
     * mail.imap.sasl.authorizationid	串	要在SASL身份验证中使用的授权标识。如果未设置，则使用验证ID（用户名）。
     * mail.imap.sasl.realm	串	用于需要领域的SASL身份验证机制的领域，例如DIGEST-MD5。
     * mail.imap.auth.ntlm.domain	串	NTLM身份验证域。
     * mail.imap.auth.ntlm.flags	INT	NTLM协议特定的标志。
     * mail.imap.socketFactory	套接字工厂	如果设置为实现javax.net.SocketFactory接口的类，则此类将用于创建IMAP套接字。
     * mail.imap.socketFactory.class	串	如果设置，则指定实现javax.net.SocketFactory接口的类的名称。该类将用于创建IMAP套接字。
     * mail.imap.socketFactory.fallback	布尔	如果设置为true，则无法使用指定的套接字工厂类创建套接字将导致使用java.net.Socket类创建套接字。默认为true。
     * mail.imap.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。未设置时使用默认端口。
     * mail.imap.ssl.enable	布尔	如果设置为true，则默认情况下使用SSL连接并使用SSL端口。“imap”协议默认为false，“imaps”协议默认为true。
     * mail.imap.ssl.checkserveridentity	布尔	如果设置为true，请检查RFC 2595指定的服务器标识。默认为false。
     * mail.imap.ssl.trust	串	如果设置，并且尚未指定套接字工厂，则启用MailSSLSocketFactory。
     * 如果设置为“*”，则所有主机都是可信的。
     * 如果设置为以空格分隔的主机列表，则这些主机是受信任的。
     * 否则，信任取决于服务器提供的证书。
     * mail.imap.ssl.socketFactory	SSL套接字工厂	如果设置为扩展javax.net.ssl.SSLSocketFactory类的类，则此类将用于创建IMAP SSL套接字。
     * mail.imap.ssl.socketFactory.class	串	如果设置，则指定扩展javax.net.ssl.SSLSocketFactory类的类的名称。此类将用于创建IMAP SSL套接字。
     * mail.imap.ssl.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。如果未设置，将使用默认端口。
     * mail.imap.ssl.protocols	串	指定将为SSL连接启用的SSL协议。属性值是javax.net.ssl.SSLSocket.setEnabledProtocols方法可接受的以空格分隔的标记列表。
     * mail.imap.starttls.enable	布尔	如果为true，则允许使用STARTTLS命令（如果服务器支持）在发出任何登录命令之前将连接切换到受TLS保护的连接。默认值为false。
     * mail.imap.starttls.required	布尔	如果为true，则需要使用STARTTLS命令。如果服务器不支持STARTTLS命令，或者命令失败，则connect方法将失败。默认为false。
     * mail.imap.socks.host	串	指定将用于连接到邮件服务器的SOCKS5代理服务器的主机名。
     * mail.imap.socks.port	串	指定SOCKS5代理服务器的端口号。如果代理服务器未使用标准端口号1080，则只需使用此选项。
     * mail.imap.minidletime	INT	此属性设置延迟（以毫秒为单位）。如果未设置，则默认值为10毫秒。
     * mail.imap.enableimapevents	布尔	启用特殊的IMAP特定事件以传递到Store的ConnectionListener。如果为true，则在Store的idle方法期间收到的未经请求的响应将作为ConnectionEvents发送，其类型为IMAPStore.RESPONSE。事件的消息将是原始IMAP响应字符串。默认情况下，不会发送这些事件。
     * mail.imap.folder.class	串	com.sun.mail.imap.IMAPFolder的子类的类名。子类可用于为其他IMAP命令提供支持。子类必须具有public MyIMAPFolder（String fullName，char separator，IMAPStore store，Boolean isNamespace）和public MyIMAPFolder（ListInfo li，IMAPStore store）形式的公共构造函数
     * <p>
     * <p>
     * <p>
     * <p>
     * <p>
     * mail.pop3.user	串	POP3的默认用户名。
     * mail.pop3.host	串	要连接的POP3服务器。
     * mail.pop3.port	INT	要连接的POP3服务器端口，如果connect（）方法没有显式指定一个。默认为110。
     * mail.pop3.connectiontimeout	INT	套接字连接超时值，以毫秒为单位。默认为无限超时。
     * mail.pop3.timeout	INT	套接字I / O超时值，以毫秒为单位。默认为无限超时。
     * mail.pop3.rsetbeforequit	布尔	在发送QUIT命令之前，在关闭文件夹时发送POP3 RSET命令。默认值为false。
     * mail.pop3.message.class	串	com.sun.mail.pop3.POP3Message的子类的类名。子类可用于处理（例如）非标准Content-Type标头。子类必须具有MyPOP3Message（Folder f，int msgno）形式的公共构造函数抛出MessagingException。
     * mail.pop3.localaddress	串	创建POP3套接字时要绑定的本地地址（主机名）。默认为Socket类选择的地址。
     * mail.pop3.localport	INT	创建POP3套接字时要绑定的本地端口号。默认为Socket类选择的端口号。
     * mail.pop3.apop.enable	布尔	如果设置为true，如果POP3服务器支持APOP，则使用APOP而不是USER / PASS登录POP3服务器。APOP发送密码摘要而不是明文密码。默认为false。
     * mail.pop3.socketFactory	套接字工厂	如果设置为实现javax.net.SocketFactory接口的类，则此类将用于创建POP3套接字。
     * mail.pop3.socketFactory.class	串	如果设置，则指定实现javax.net.SocketFactory接口的类的名称。该类将用于创建POP3套接字。
     * mail.pop3.socketFactory.fallback	布尔	如果设置为true，则无法使用指定的套接字工厂类创建套接字将导致使用java.net.Socket类创建套接字。默认为true。
     * mail.pop3.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。如果未设置，将使用默认端口。
     * mail.pop3.ssl.enable	布尔	如果设置为true，则默认情况下使用SSL连接并使用SSL端口。“pop3”协议默认为false，“pop3s”协议默认为true。
     * mail.pop3.ssl.checkserveridentity	布尔	如果设置为true，请检查RFC 2595指定的服务器标识。默认为false。
     * mail.pop3.ssl.trust	串	如果设置，并且尚未指定套接字工厂，则启用MailSSLSocketFactory。
     * 如果设置为“*”，则所有主机都是可信的。
     * 如果设置为以空格分隔的主机列表，则这些主机是受信任的。
     * 否则，信任取决于服务器提供的证书。
     * mail.pop3.ssl.socketFactory	SSL套接字工厂	如果设置为扩展javax.net.ssl.SSLSocketFactory类的类，则此类将用于创建POP3 SSL套接字。
     * mail.pop3.ssl.socketFactory.class	串	如果设置，则指定扩展javax.net.ssl.SSLSocketFactory类的类的名称。此类将用于创建POP3 SSL套接字。
     * mail.pop3.ssl.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。如果未设置，将使用默认端口。
     * mail.pop3.ssl.protocols	串	指定将为SSL连接启用的SSL协议。属性值是javax.net.ssl.SSLSocket.setEnabledProtocols方法可接受的以空格分隔的标记列表。
     * mail.pop3.starttls.enable	布尔	如果为true，则允许使用STLS命令（如果服务器支持）在发出任何登录命令之前将连接切换到受TLS保护的连接。默认为false。
     * mail.pop3.starttls.required	布尔	如果为true，则需要使用STLS命令。如果服务器不支持STLS命令，或者命令失败，则connect方法将失败。默认为false。
     * mail.pop3.socks.host	串	指定将用于连接到邮件服务器的SOCKS5代理服务器的主机名。
     * mail.pop3.socks.port	串	指定SOCKS5代理服务器的端口号。
     * mail.pop3.disabletop	布尔	如果设置为true，则不会使用POP3 TOP命令来获取邮件头。默认为false。
     * mail.pop3.forgettopheaders	布尔	如果设置为true，则将忘记使用POP3 TOP命令检索的标头，并将其替换为作为POP3 RETR命令的一部分检索的标头。默认为false。
     * mail.pop3.filecache.enable	布尔	如果设置为true，POP3提供程序将将消息数据缓存在临时文件中而不是内存中。消息仅在访问消息内容时添加到缓存中。消息头始终缓存在内存中（按需）。文件夹关闭或JVM终止时，将删除文件缓存。默认为false。
     * mail.pop3.filecache.dir	串	如果启用了文件高速缓存，则可以使用此属性覆盖JDK用于临时文件的缺省目录。
     * mail.pop3.cachewriteto	布尔	控制writeTo方法在POP3消息对象上的行为。如果设置为true，并且消息内容尚未缓存，并且ignoreList为null，则在写入之前缓存该消息。否则，消息将直接流式传输到输出流而不进行高速缓存。默认为false。
     * mail.pop3.keepmessagecontent	布尔	如果此属性设置为true，则将保留对缓存内容的硬引用，从而阻止在文件夹关闭或缓存内容显式无效之前重用内存（使用invalidate方法）。默认为false。
     *
     * @return
     */
    private Properties getProperties() {
        Properties properties = new Properties();

        // default setting
        String mailType = mailBoxService.getProtocol();
        String protocol = mailBoxService.getProtocol();
        // 这里不设置的时候就默认是110
//        properties.put(String.format("mail.%s.port", protocol), mailBoxService.getPort() + "");

//        properties.put("mail.debug", "true");
//        properties.put(String.format("mail.%s.host", protocol), mailBoxService.getHost());
//        properties.put(String.format("mail.%s.auth", protocol), "true");
//        properties.put(String.format("mail.%s.protocol", protocol), mailBoxService.getProtocol());
//        properties.put(String.format("mail.%s.connectiontimeout", protocol), "5000");
//        properties.put(String.format("mail.%s.timeout", protocol), "5000");
//        properties.put(String.format("mail.%s.connectionpooltimeout", protocol), "5000");


        properties.setProperty("mail.imap.host", mailBoxService.getHost());
        // 控制是否应使用IMAP部分提取功能。默认为true。 这里会影响到关于附件的下载速度问题，个人建议关闭。
        properties.setProperty("mail.imap.partialfetch", true + "");
        // properties.setProperty("mail.imap.fetchsize", 1024 +"");
        properties.setProperty("mail.imap.connectiontimeout", String.valueOf(TIMEOUT_30_SECOND));
        properties.setProperty("mail.imap.timeout", String.valueOf(TIMEOUT_30_SECOND));
        properties.setProperty("mail.imap.connectionpooltimeout", String.valueOf(TIMEOUT_30_SECOND));
        properties.setProperty("mail.mime.decodetext.strict", false + "");
        // 地址严格要求
        properties.setProperty("mail.mime.address.strict", "false");
//            p.setProperty("mail.imap.port", Integer.toString(port));

        // 只能使用非25默认端口的，因为阿里服务有限制
        properties.setProperty("mail.imap.socketFactory.port", Integer.toString(mailBoxService.getPort()));
        properties.setProperty("mail.imap.ssl.enable", "true");
        properties.setProperty("mail.imap.ssl.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

        // 预留
        if ("IMAP".equals(mailType.toUpperCase())) {
        } else if ("POP3".equals(mailType.toUpperCase())) {
        }

        // 使用ssl认证方式
        if (mailBoxService.getSsl()) {
            // ssl set
//            properties.setProperty(String.format("mail.%s.socketFactory.port", protocol), Integer.toString(mailBoxService.getPort()));
//            properties.setProperty(String.format("mail.%s.ssl.enable", protocol), "true");
//            properties.setProperty(String.format("mail.%s.ssl.socketFactory.class", protocol), "javax.net.ssl.SSLSocketFactory");
//            properties.setProperty(String.format("mail.%s.socketFactory.fallback", properties), "false");

            // 预留
            if ("IMAP".equals(mailType.toUpperCase())) {
            } else if ("POP3".equals(mailType.toUpperCase())) {
            }
        }
        return properties;
    }

    /**
     * imap 环境变量配置
     *
     * @param properties
     */
    private void setImapProperties(Properties properties) {

    }


    /**
     * 根据配置文件获取mail的session，用户连接邮件服务器
     *
     * @return
     */
    private Session getMailSession() {
        Session session = Session.getDefaultInstance(properties, null);
        return session;
    }


    /**
     * 获取邮件
     *
     * @return
     */
    private Store getStore() {
        try {
            Session session = Session.getDefaultInstance(getProperties(), null);
            return session.getStore(mailBoxService.getProtocol());
        } catch (NoSuchProviderException e) {
        }
        return null;
    }


    /**
     * 判断邮件中是否包含附件
     *
     * @param part 邮件内容
     * @return 邮件中存在附件返回true，不存在返回false
     * @throws MessagingException
     * @throws IOException
     */
    public static boolean isContainAttachment(Part part) throws MessagingException, IOException {
        boolean flag = false;
        if (part == null) {
            System.out.println("111111111111111111111111111111111111");
        }
        if (part.isMimeType("multipart/*")) {
            MimeMultipart multipart = (MimeMultipart) part.getContent();
            int partCount = multipart.getCount();
            for (int i = 0; i < partCount; i++) {
                BodyPart bodyPart = multipart.getBodyPart(i);
                String disp = bodyPart.getDisposition();
                if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
                    flag = true;
                } else if (bodyPart.isMimeType("multipart/*")) {
                    flag = isContainAttachment(bodyPart);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.indexOf("application") != -1) {
                        flag = true;
                    }

                    if (contentType.indexOf("name") != -1) {
                        flag = true;
                    }
                }

                if (flag) {
                    break;
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            flag = isContainAttachment((Part) part.getContent());
        }
        return flag;
    }


    /**
     * 保存附件
     *
     * @param part    邮件中多个组合体中的其中一个组合体
     * @param destDir 附件保存目录
     * @throws UnsupportedEncodingException
     * @throws MessagingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void saveAttachment(Part part, String destDir) throws UnsupportedEncodingException, MessagingException,
            FileNotFoundException, IOException {
        if (part.isMimeType("multipart/*")) {
            Multipart multipart = (Multipart) part.getContent();    //复杂体邮件
            //复杂体邮件包含多个邮件体
            int partCount = multipart.getCount();
            for (int i = 0; i < partCount; i++) {
                //获得复杂体邮件中其中一个邮件体
                BodyPart bodyPart = multipart.getBodyPart(i);
                //某一个邮件体也有可能是由多个邮件体组成的复杂体
                String disp = bodyPart.getDisposition();
                if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
                    InputStream is = bodyPart.getInputStream();
                    saveFile(is, destDir, decodeText(bodyPart.getFileName()));
                } else if (bodyPart.isMimeType("multipart/*")) {
                    saveAttachment(bodyPart, destDir);
                } else {
                    String contentType = bodyPart.getContentType();
                    if (contentType.indexOf("name") != -1 || contentType.indexOf("application") != -1) {
                        saveFile(bodyPart.getInputStream(), destDir, decodeText(bodyPart.getFileName()));
                    }
                }
            }
        } else if (part.isMimeType("message/rfc822")) {
            saveAttachment((Part) part.getContent(), destDir);
        }
    }


    /**
     * 保存附件
     *
     * @param inputStream
     * @param destDir
     * @param fileName
     */
    private static void saveFile(InputStream inputStream, String destDir, String fileName) {
        if (StringUtils.isEmpty(fileName)) {
            return;
        }
        fileName = fileName.replace(" ","");
        System.out.println("path:" + destDir + File.separator + fileName.trim());
        File file = new File(destDir + File.separator + fileName);
        Path path2 = Paths.get(destDir + File.separator + fileName.trim());
        try {
//            Files.copy(inputStream, path2, REPLACE_EXISTING);
            long copys = copy(inputStream, new FileOutputStream(destDir + File.separator + fileName));
            System.out.println("fileSize : " + copys);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        } finally {

        }
    }


    /**
     * 文件复制
     *
     * @param source
     * @param sink   <br/>
     * @return long
     * @Author: timo
     * @Date: 2019/7/11 11:13
     */
    private static long copy(InputStream source, OutputStream sink)
            throws IOException {

        long nread = 0L;
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = source.read(buf)) > 0) {
            sink.write(buf, 0, n);
            nread += n;
        }

        return nread;
    }


    /**
     * 文本解码
     *
     * @param encodeText 解码MimeUtility.encodeText(String text)方法编码后的文本
     * @return 解码后的文本
     * @throws UnsupportedEncodingException
     */
    public static String decodeText(String encodeText) throws UnsupportedEncodingException {
        if (encodeText == null || "".equals(encodeText)) {
            return "";
        } else {
            return MimeUtility.decodeText(encodeText);
        }
    }


    /**
     * 保存邮件中的附件
     *
     * @return
     */
    private List<MailFile> saveMailFile(String saveAttachPath) {
        return null;
    }

    /**
     * 判断是否是回退邮件，因为回退邮件的需要特殊的处理
     *
     * @param messageId 邮件ID
     * @return true 是回退邮件 / false 不是回退邮件
     */
    private boolean goBackMail(String messageId) {
        if (StringUtils.isNotBlank(messageId) && messageId.indexOf("report") >= 0) {
            return true;
        }

        return false;
    }


    /**
     * 因为获取的邮件都是按照时间倒叙/正序来的，只需要判断第一条不符合的就直接返回了。
     *
     * @param messages
     * @return
     */
    private Message[] getToDayMessage(Message[] messages) {
        ArrayList<Message> result = new ArrayList<>();
        Message headMessage = messages[0];
        Message endMessage = messages[messages.length - 1];
        try {
            // 判断如果最后一条消息的接收日期小于第一条消息的日期，就调转,这里有个地方需要考虑一下，
            // 接收日期/发送日期可能会为null
            Date headDate = Optional.ofNullable(endMessage.getReceivedDate()).orElse(endMessage.getSentDate());
            Date endDate = Optional.ofNullable(headMessage.getReceivedDate()).orElse(headMessage.getSentDate());
            if (headDate.after(endDate)) {
                for (int i = messages.length - 1; i >= 0; i--) {
                    Date date = Optional.ofNullable(messages[i].getReceivedDate()).orElse(messages[i].getSentDate());
                    if (isToDay(date)) {
                        result.add(messages[i]);
                    } else {
                        break;
                    }
                }
            } else {
                for (int i = 0; i < messages.length; i++) {
                    if (isToDay(messages[i].getReceivedDate())) {
                        result.add(messages[i]);
                    } else {
                        break;
                    }
                }
            }


        } catch (MessagingException e) {

        }
        return result.toArray(new Message[0]);
    }


    /**
     * 获取新邮件
     *
     * @param folder 收件箱 <br/>
     * @return javax.mail.Message[]
     * @Author: timo
     * @Date: 2019/7/11 11:25
     */
    private Message[] getInboxNewMessage(Folder folder) throws MessagingException {
        if (folder == null) {
            return null;
        }

        int newCount = folder.getNewMessageCount();
        int allCount = folder.getMessageCount();
        return folder.getMessages(allCount - newCount, allCount);
//        return folder.getMessages(90 + 188, allCount);
    }

    /**
     * 获取新邮件
     *
     * @param folder 收件箱 <br/>
     * @return javax.mail.Message[]
     * @Author: timo
     * @Date: 2019/7/11 11:25
     */
    private Message[] getInboxNewMessage(Folder folder, boolean all) throws MessagingException {
        if (folder == null) {
            return null;
        }
        return all ? folder.getMessages() : getInboxNewMessage(folder);
//        for (Message m : folder.getMessages()) {
//            if ("Call between Renice and Teleplan on testers".equals(m.getSubject())) {
//                return new Message[]{m};
//            }
//        }
//        return null;
    }

    /**
     * @param date
     * @return
     */
    private boolean isToDay(Date date) {
        if (date == null) {
            return false;
        }
        LocalDate localDate = date
                .toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate();

        LocalDate today = LocalDate.now();
        if (localDate.equals(today)) {
            return true;
        }
        return false;
    }


    /**
     * 获取邮件消息
     *
     * @param message <br/>
     * @return javax.mail.internet.MimeMessage
     * @Author: timo
     * @Date: 2019/7/11 11:14
     */
    private Message getMailMimeMessage(MimeMessage message) throws MessagingException {
        if (message != null) {
            try {
                // 判断是否是回退邮件
                if (goBackMail(message.getMessageID())) {
                    message = new MimeMessage((MimeMessage) message);
                }
            } catch (MessagingException e) {
                e.printStackTrace();
            }
        }


        return new MimeMessage(message);
    }


    /**
     * 判断当前的list不是空的
     *
     * @param list
     * @return true 非空 / false 空
     */
    private boolean listIsNotBlank(List list) {
        if (!listIsEmpty(list)) {
            return false;
        }

        return true;
    }


    /**
     * 判断当前的list是空的
     *
     * @param list
     * @return true 空 / false 非空
     */
    private boolean listIsEmpty(List list) {
        if (list == null || list.size() == 0) {
            return true;
        }

        return false;
    }


    /**
     * 获得送信人的姓名和邮件地址
     */
    public InternetAddress getFrom() throws MessagingException {
        InternetAddress[] address = null;
        if (null != mimeMessage) {
            address = (InternetAddress[]) mimeMessage.getFrom();
        }
        if (address == null) {
            String[] from = mimeMessage.getHeader("From");
            address = new InternetAddress[]{new InternetAddress(from[0])};
        }
        if(null != address && address.length > 0){
            return address[0];
        }
        return null;
    }


    public String getFromStr(MimeMessage mimeMessage) throws MessagingException {
        final String ADDRESS_RESULT = "%s<%s>";
//        InternetAddress[] address = null;
//        if (null != mimeMessage) {
//            address = (InternetAddress[]) mimeMessage.getFrom();
//        }
//        if (address == null) {
//            String[] from = mimeMessage.getHeader("From");
//            address = new InternetAddress[]{new InternetAddress(from[0])};
//        }
//
//        if (address == null || address.length == 0) {
//            return "";
//        }


        InternetAddress address[] = (InternetAddress[]) mimeMessage.getFrom();
        String from = Optional.ofNullable(address[0].getAddress()).orElse("");
        String personal = Optional.ofNullable(address[0].getPersonal()).orElse("");
        return String.format(ADDRESS_RESULT, personal, from);
    }


    public String getMailAddress(MimeMessage mimeMessage) throws Exception {
        InternetAddress[] address = new InternetAddress[]{};
        String mailAddress = "";

        if (null != mimeMessage) {

            String[] tos = mimeMessage.getHeader("TO");
            String to = tos[0];

            if (to.lastIndexOf(";") == to.length() - 1) {
                to = to.substring(0, to.length() - 1);
                mimeMessage.setHeader("TO",to);
            }

            address = (InternetAddress[]) mimeMessage.getRecipients(Message.RecipientType.TO);
            for (InternetAddress a : address) {
                String toUserAddress = StringUtils.isBlank(a.getAddress()) ? "" : MimeUtility.decodeText(a.getAddress());
                String toUserPersonal = StringUtils.isBlank(a.getPersonal()) ? "" : MimeUtility.decodeText(a.getPersonal());
                mailAddress += ADDRESS_SEPARATOR + String.format(ADDRESS_RESULT, toUserPersonal, toUserAddress);
            }
        }
        return mailAddress;
    }




    public static void main(String[] args) {
        imap_test();
    }

    public static void imap_test() {


        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("youmail");
        mailBox.setPassword("password");


        mailBox.setPort(993);
        mailBox.setSsl(true);

        ReceiveMailHandler receiveMailHandler = new ReceiveMailHandler(mailBox);
        boolean connect = receiveMailHandler.connect();
        System.out.println("连接：" + connect);

//        receiveMailHandler.receiveNewMail(Arrays.asList("11111111111"));
//        receiveMailHandler.receiveNewMail(Arrays.asList("1111111"));
        receiveMailHandler.receiveNewMailThread(Arrays.asList());
    }

    public static void pop3_test() {
        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("youmail");
        mailBox.setPassword("password");
        mailBox.setProtocol("pop3");
        mailBox.setHost("pop.exmail.qq.com");
        mailBox.setPort(995);
        mailBox.setSsl(true);

        ReceiveMailHandler receiveMailHandler = new ReceiveMailHandler(mailBox);
        boolean connect = receiveMailHandler.connect();
        System.out.println("连接：" + connect);

        receiveMailHandler.receiveNewMail(new ArrayList<String>());

    }


}
